using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using IWshRuntimeLibrary;

namespace ViewOn.Helper.Install
{
    /// <summary>
    /// The installation of the shortcuts is done this way as the VS.NET Setup Project
    /// does not permit a desktop shortcut to be conditionally created and it does not
    /// provide for creating a Quick Launch shortcut.
    /// The caption for the shortcut is set to the AssemblyTitle attribute for the 
    /// current assembly. If the AssemblyTitle attribute is empty, the caption is just
    /// set to the exe name.
    /// The description for the shortcut is set to the AssemblyDescription attribute
    /// for the current assembly. If the AssemblyDescription attribute is empty, the 
    /// description is set to "Launch {caption}".
    /// </summary>

    /// http://www.codeproject.com/dotnet/shortcut_installer.asp
    [RunInstaller(true)]
    public class ShortcutsInstaller : Installer
    {

        #region Private Instance Variables

        private string _location = null;
        private string _name = null;
        private string _description = null;

        #endregion


        #region Private Properties

        private string QuickLaunchFolder
        {
            get
            {
                return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
                    "\\Microsoft\\Internet Explorer\\Quick Launch";
            }
        }


        private string ShortcutTarget
        {
            get
            {
                if (_location == null)
                    _location = Assembly.GetExecutingAssembly().Location;
                    
                FileInfo fi = new FileInfo(_location);

                return fi.Directory.FullName + "\\viewon.exe";
            }
        }


        private string ShortcutName
        {
            get
            {
				//if (_name == null)
				//{
				//    Assembly myAssembly = Assembly.GetExecutingAssembly();

				//    try
				//    {
				//        object titleAttribute = myAssembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false)[0];
				//        _name = ((AssemblyTitleAttribute)titleAttribute).Title;
				//    }
				//    catch { }

				//    if ((_name == null) || (_name.Trim() == string.Empty))
				//        _name = myAssembly.GetName().Name;
				//}
				//return _name;
				return "ViewOn.tv";
            }
        }


        private string ShortcutDescription
        {
            get
            {
                if (_description == null)
                {
                    Assembly myAssembly = Assembly.GetExecutingAssembly();

                    try
                    {
                        object descriptionAttribute = myAssembly.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false)[0];
                        _description = ((AssemblyDescriptionAttribute)descriptionAttribute).Description;
                    }
                    catch { }

                    if ((_description == null) || (_description.Trim() == string.Empty))
                        _description = "Launch " + ShortcutName;
                }
                return _description;
            }
        }


        #endregion


        #region Override Methods

        public override void Install(IDictionary savedState)
        {
            try
            {
                base.Install(savedState);

                const string DESKTOP_SHORTCUT_PARAM = "DESKTOP_SHORTCUT";
                const string QUICKLAUNCH_SHORTCUT_PARAM = "QUICKLAUNCH_SHORTCUT";
                const string ALLUSERS_PARAM = "ALLUSERS";

                // The installer will pass the ALLUSERS, DESKTOP_SHORTCUT and QUICKLAUNCH_SHORTCUT   
                // parameters. These have been set to the values of radio buttons and checkboxes from the
                // MSI user interface.
                // ALLUSERS is set according to whether the user chooses to install for all users (="1") 
                // or just for themselves (="").
                // If the user checked the checkbox to install one of the shortcuts, then the corresponding 
                // parameter value is "1".  If the user did not check the checkbox to install one of the 
                // desktop shortcut, then the corresponding parameter value is an empty string.

                // First make sure the parameters have been provided.
                if (!Context.Parameters.ContainsKey(ALLUSERS_PARAM))
                    throw new Exception(string.Format("The {0} parameter has not been provided for the {1} class.", ALLUSERS_PARAM, this.GetType()));
                if (!Context.Parameters.ContainsKey(DESKTOP_SHORTCUT_PARAM))
                    throw new Exception(string.Format("The {0} parameter has not been provided for the {1} class.", DESKTOP_SHORTCUT_PARAM, this.GetType()));
                if (!Context.Parameters.ContainsKey(QUICKLAUNCH_SHORTCUT_PARAM))
                    throw new Exception(string.Format("The {0} parameter has not been provided for the {1} class.", QUICKLAUNCH_SHORTCUT_PARAM, this.GetType()));

                bool allusers = Context.Parameters[ALLUSERS_PARAM] != string.Empty;
                bool installDesktopShortcut = Context.Parameters[DESKTOP_SHORTCUT_PARAM] != string.Empty;
                bool installQuickLaunchShortcut = Context.Parameters[QUICKLAUNCH_SHORTCUT_PARAM] != string.Empty;

                if (installDesktopShortcut)
                {
                    // If this is an All Users install then we need to install the desktop shortcut for 
                    // all users.  .Net does not give us access to the All Users Desktop special folder,
                    // but we can get this using the Windows Scripting Host.
                    string desktopFolder = null;

                    if (allusers)
                    {
                        try
                        {
                            // This is in a Try block in case AllUsersDesktop is not supported
                            object allUsersDesktop = "AllUsersDesktop";
                            WshShell shell = new WshShellClass();
                            desktopFolder = shell.SpecialFolders.Item(ref allUsersDesktop).ToString();
                        }
                        catch { }
                    }
                    if (desktopFolder == null)
                        desktopFolder = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);

                    CreateShortcut(desktopFolder, ShortcutName, ShortcutTarget, ShortcutDescription);
                }

                if (installQuickLaunchShortcut)
                {
                    CreateShortcut(QuickLaunchFolder, ShortcutName, ShortcutTarget, ShortcutDescription);
                }
            }
            catch
            {
            }
        }


        public override void Uninstall(IDictionary savedState)
        {
            base.Uninstall(savedState);

            DeleteShortcuts();
        }


        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);

            DeleteShortcuts();
        }

        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);

            const string START_VIEWON_PARAM = "START_VIEWON";

            // Start the application ?
            if (!Context.Parameters.ContainsKey(START_VIEWON_PARAM))
                throw new Exception(string.Format("The {0} parameter has not been provided for the {1} class.", START_VIEWON_PARAM, this.GetType()));

            bool start = Context.Parameters[START_VIEWON_PARAM] != string.Empty;

            if (start)
            {
                try
                {
                    //System.Diagnostics.Process.Start(Assembly.GetExecutingAssembly().Location);
                    ProcessStartInfo psi = new ProcessStartInfo();
                    psi.FileName = Assembly.GetExecutingAssembly().Location;
                    psi.WorkingDirectory = new FileInfo(psi.FileName).DirectoryName;
                    //psi.LoadUserProfile = true;

                    //psi.UseShellExecute = false;
                    //psi.RedirectStandardOutput = true;
                    //psi.RedirectStandardError = true;
                    //psi.CreateNoWindow = true;

                    psi.LoadUserProfile = true;
                    Process p = Process.Start(psi);

                    //string fileName = Assembly.GetExecutingAssembly().Location;
                    //string workingDirectory = new FileInfo(fileName).DirectoryName;
                    //CreateProcessAsUser(workingDirectory, fileName);
                }
                catch { }
            }
        }


        #endregion


        #region Private Helper Methods

        private void CreateShortcut(string folder, string name, string target, string description)
        {
            string shortcutFullName = Path.Combine(folder, name + ".lnk");

            string installFolder = Context.Parameters["assemblypath"];
            installFolder = installFolder.Substring(0, installFolder.LastIndexOf("\\")) + "\\";

            try
            {
                WshShell shell = new WshShellClass();
                IWshShortcut link = (IWshShortcut)shell.CreateShortcut(shortcutFullName);
                link.TargetPath = target;
                link.Description = description;
                link.IconLocation = Path.Combine(installFolder, "ViewOn.ico");
                link.WorkingDirectory = installFolder;
                link.Save();
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("The shortcut \"{0}\" could not be created.\n\n", shortcutFullName),
                    "Create Shortcut", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }


        private void DeleteShortcuts()
        {
            // Just try and delete all possible shortcuts that may have been
            // created during install

            try
            {
                // This is in a Try block in case AllUsersDesktop is not supported
                object allUsersDesktop = "AllUsersDesktop";
                WshShell shell = new WshShellClass();
                string desktopFolder = shell.SpecialFolders.Item(ref allUsersDesktop).ToString();
                DeleteShortcut(desktopFolder, ShortcutName);
            }
            catch { }

            DeleteShortcut(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), ShortcutName);

            DeleteShortcut(QuickLaunchFolder, ShortcutName);
        }


        private void DeleteShortcut(string folder, string name)
        {
            string shortcutFullName = Path.Combine(folder, name + ".lnk");
            FileInfo shortcut = new FileInfo(shortcutFullName);
            if (shortcut.Exists)
            {
                try
                {
                    shortcut.Delete();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(string.Format("The shortcut \"{0}\" could not be deleted.\n\n{1}", shortcutFullName, ex.ToString()),
                        "Delete Shortcut", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }


        #endregion

        #region CreateProcessAsUser

        private void CreateProcessAsUser(string currentDirectory, string appName)
        {
            IntPtr hToken = System.Security.Principal.WindowsIdentity.GetCurrent().Token;
            IntPtr hDupedToken = IntPtr.Zero;

            ProcessUtility.PROCESS_INFORMATION pi = new ProcessUtility.PROCESS_INFORMATION();

            try
            {
                ProcessUtility.SECURITY_ATTRIBUTES sa = new ProcessUtility.SECURITY_ATTRIBUTES();
                sa.Length = Marshal.SizeOf(sa);

                bool result = ProcessUtility.DuplicateTokenEx(
                      hToken,
                      ProcessUtility.GENERIC_ALL_ACCESS,
                      ref sa,
                      (int)ProcessUtility.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                      (int)ProcessUtility.TOKEN_TYPE.TokenPrimary,
                      ref hDupedToken
                   );

                if (!result)
                {
                    throw new ApplicationException("DuplicateTokenEx failed");
                }


                ProcessUtility.STARTUPINFO si = new ProcessUtility.STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = String.Empty;

                result = ProcessUtility.CreateProcessAsUser(
                                     hDupedToken,
                                     appName,
                                     String.Empty,
                                     ref sa,
                                     ref sa,
                                     false,
                                     0,
                                     IntPtr.Zero,
                                     currentDirectory,
                                     ref si,
                                     ref pi
                               );

                if (!result)
                {
                    int error = Marshal.GetLastWin32Error();
                    string message = String.Format("CreateProcessAsUser Error: {0}", error);
                    throw new ApplicationException(message);
                }
            }
            finally
            {
                if (pi.hProcess != IntPtr.Zero)
                    ProcessUtility.CloseHandle(pi.hProcess);
                if (pi.hThread != IntPtr.Zero)
                    ProcessUtility.CloseHandle(pi.hThread);
                if (hDupedToken != IntPtr.Zero)
                    ProcessUtility.CloseHandle(hDupedToken);
            }
        }

        #endregion

    }

    #region ProcessUtility

    public class ProcessUtility
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public Int32 dwProcessID;
            public Int32 dwThreadID;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        public enum SECURITY_IMPERSONATION_LEVEL
        {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }

        public enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }

        public const int GENERIC_ALL_ACCESS = 0x10000000;

        [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
        public static extern bool
           CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
                               ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
                               bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
                               string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
                               ref PROCESS_INFORMATION lpProcessInformation);

        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
        public static extern bool
           DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
                            ref SECURITY_ATTRIBUTES lpThreadAttributes,
                            Int32 ImpersonationLevel, Int32 dwTokenType,
                            ref IntPtr phNewToken);
    }

    #endregion

}
